﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Windows;
using System.Diagnostics;
using System.Threading;
using Wpf.Telemetry.TelemetryService;
using System.ServiceModel.Channels;
using System.ServiceModel;
using System.ServiceModel.Security;
using System.Management;

namespace Wpf.Telemetry
{
    public class Monitoring : IDisposable
    {
        private Dictionary<string, int> featureUsageDictionary = new Dictionary<string, int>();
        private Timer featureUsageReportingTimer;
        private SystemInformation systemInfo;
        readonly ITelemetryReporter reporter;
        private static readonly Guid sessionId = Guid.NewGuid(); // we want this to be static for the lifetime of the process
        private string processor;

        public Monitoring(int featureUsageReportingPeriodMinutes, ITelemetryReporter reporter)
        {
            if (reporter == null)
            {
                throw new ArgumentNullException("reporter");
            }

            this.reporter = reporter;
            ConfigureFeatureReportingTimer(featureUsageReportingPeriodMinutes);
        }

        private void ConfigureFeatureReportingTimer(int featureUsageReportingPeriodMinutes)
        {
            var usageReportingTime = new TimeSpan(0, featureUsageReportingPeriodMinutes, 0);
            featureUsageReportingTimer = new Timer(new TimerCallback(this.ReportFeatureUsage), null, usageReportingTime, usageReportingTime);
        }

        public void RecordFeatureUsage(string featureName)
        {
            if (featureUsageDictionary.ContainsKey(featureName))
            {
                featureUsageDictionary[featureName]++;
            }
            else
            {
                featureUsageDictionary.Add(featureName, 1); // this is the first time this feature has been used - give it a count of 1
            }
        }

        public void RecordFeatureUsage(string featureName, object source)
        {
            var sourceUI = source as FrameworkElement;
            string additionalInfo = "";
            if (sourceUI != null && !string.IsNullOrEmpty(sourceUI.Name))
            {
                additionalInfo = sourceUI.Name;
            }
            else
            {
                additionalInfo = source.ToString();
            }

            RecordFeatureUsage(featureName + " " + additionalInfo);
        }

        public void SendErrorReport(Exception ex, string errorDescriptionFromUser, string emailAddress)
        {
            try
            {
                var info = new ExceptionInformation();
                info.CallStack = ex.StackTrace;
                info.ErrorDescriptionFromUser = errorDescriptionFromUser;
                info.Message = ex.Message;
                info.ExceptionName = ex.GetType().ToString();
                info.EmailAddress = emailAddress;
                reporter.ReportException(this.SystemInformation, info);
            }
            catch (Exception sendEx)
            {
#if DEBUG
                Debug.WriteLine("Unable to send unhandled exception report");
                Debug.WriteLine(sendEx);
#endif
            }
        }

        public void ReportFeatureUsage()
        {
            ReportFeatureUsage(null);
        }

        private SystemInformation SystemInformation
        {
            get { return systemInfo ?? (systemInfo = GetSystemInformation()); }
        }

        private Dictionary<string, int> GetFeatureUsageAndReset()
        {
            var currentFeatureUsage = featureUsageDictionary;
            featureUsageDictionary = new Dictionary<string, int>();
            return currentFeatureUsage;
        }

        private SystemInformation GetSystemInformation()
        {
            var info = new SystemInformation();
            info.IsSlowMachine = SystemParameters.IsSlowMachine;
            info.IsTabletPc = SystemParameters.IsTabletPC;
            info.OperatingSystem = Environment.OSVersion.ToString();
            if (processor == null)
            {
                processor = GetProcessorInfo();
            }
            info.ProcessorArchitecture = processor;
            info.ProcessorCount = System.Environment.ProcessorCount;
            info.RenderCapabilityTier = System.Windows.Media.RenderCapability.Tier;
            info.ScreenHeight = (int)SystemParameters.WorkArea.Height;
            info.ScreenWidth = (int)SystemParameters.WorkArea.Width;
            info.UserNameHash = Environment.UserName.GetHashCode();
            info.ComputerNameHash = Environment.MachineName.GetHashCode();
            info.DomainNameHash = Environment.UserDomainName.GetHashCode();
            info.CLRVersion = Environment.Version.ToString(4);
            info.PhysicalMemory = NativeMethods.GetTotalPhysicalMemory();

            return info;
        }

        private void ReportFeatureUsage(object ignored)
        {
#if DEBUG
            var sw = new Stopwatch();
            sw.Start();
#endif
            try
            {
                Dictionary<string, int> featureUsage = GetFeatureUsageAndReset();

                if (featureUsage.Count > 0)
                {
                    var features = new List<FeatureUsage>(featureUsage.Count);
                    foreach (string key in featureUsage.Keys)
                    {
                        features.Add(new FeatureUsage() { FeatureName = key, UsageCount = featureUsage[key] });
                    }

                    reporter.ReportFeatureUsage(GetApplicationInfo(), this.SystemInformation, features);
#if DEBUG
                    sw.Stop();
                    Trace.WriteLine("Sending feature usage info took " + sw.ElapsedMilliseconds.ToString() + "ms");
#endif
                }
                else
                {
#if DEBUG
                    sw.Stop(); // don't log time taken if nothing was sent
#endif                    
                }
            }
            catch (Exception ex)
            {
                // we don't want to bring down the whole app because our monitoring is not working right
                System.Diagnostics.Debug.WriteLine(ex);
            }

        }

        private ApplicationInformation GetApplicationInfo()
        {
            return new ApplicationInformation
                              {
                                  Version = Assembly.GetExecutingAssembly().GetName().Version.ToString(4),
                                  UserSession = sessionId,
                                  WorkingSet = GC.GetTotalMemory(false)
                              };

            // TODO - add more application-specific stuff here
            //      - be careful about reading application-specific stuff form UI elements here, 
            //        this will get called on a background thread (not the UI thread) and you'll get exceptions
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (featureUsageReportingTimer != null)
            {
                featureUsageReportingTimer.Dispose();
            }
        }

        // if you don't want to take a dependency on System.Management you should remove this call
        public static string GetProcessorInfo()
        {
            string processorName = "";
            using (var searcher = new ManagementObjectSearcher("SELECT Name FROM Win32_Processor"))
            {
                processorName = searcher.Get().Cast<ManagementObject>().Aggregate(processorName, (current, item) => current + ((string) item["Name"] + " "));
            }
            return processorName.Trim();
        }
    }
}
